1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.graphics.g2d.geometrybatch;
12 import hip.graphics.orthocamera;
13 import hip.hiprenderer.renderer;
14 import hip.hiprenderer.shader;
15 import hip.error.handler;
16 import hip.graphics.mesh;
17 import hip.math.matrix;
18 import hip.math.utils;
19 import hip.math.vector;
20 public import hip.api.graphics.color;
21 public import hip.api.graphics.batch;
22 
23 
24 enum defaultColor = HipColor.white;
25 
26 @HipShaderInputLayout struct HipGeometryBatchVertex
27 {
28     import hip.math.vector;
29     Vector3 vPosition;
30     // @HipShaderInputPadding float __padding = 0;
31     HipColor vColor = HipColor.white;
32 
33     static enum floatCount = HipGeometryBatchVertex.sizeof / float.sizeof;
34 }
35 
36 @HipShaderVertexUniform("Geom")
37 struct HipGeometryBatchVertexUniforms
38 {
39     Matrix4 uMVP = Matrix4.identity;
40 }
41 
42 @HipShaderFragmentUniform("FragVars")
43 struct HipGeometryBatchFragmentUniforms
44 {
45     float[4] uGlobalColor = [1,1,1,1];
46 }
47 
48 /**
49 *   This class uses the vertex layout XYZ RGBA.
50 *   it is meant to be a 2D API for drawing primitives
51 */
52 class GeometryBatch : IHipBatch
53 {
54     protected Mesh mesh;
55     protected index_t lastIndexDrawn;
56     protected index_t lastVertexDrawn;
57     protected index_t currentIndex;
58     protected index_t verticesCount;
59     protected index_t indicesCount;
60     protected HipColor currentColor;
61 
62     float managedDepth = 0;
63     HipOrthoCamera camera;
64     HipGeometryBatchVertex[] vertices;
65     index_t[] indices;
66 
67 
68     this(HipOrthoCamera camera = null, index_t verticesCount=DefaultMaxGeometryBatchVertices, index_t indicesCount=DefaultMaxGeometryBatchVertices)
69     {
70         import hip.hiprenderer.initializer;
71         Shader s = newShader(HipShaderPresets.GEOMETRY_BATCH);
72         s.addVarLayout(ShaderVariablesLayout.from!(HipGeometryBatchVertexUniforms)(HipRenderer.getInfo));
73         s.addVarLayout(ShaderVariablesLayout.from!(HipGeometryBatchFragmentUniforms)(HipRenderer.getInfo));
74         s.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD);
75 
76 
77         mesh = new Mesh(HipVertexArrayObject.getVAO!HipGeometryBatchVertex, s);
78         vertices = new HipGeometryBatchVertex[verticesCount];
79         indices = new index_t[indicesCount];
80         mesh.createVertexBuffer(verticesCount, HipResourceUsage.Dynamic);
81         mesh.createIndexBuffer(indicesCount, HipResourceUsage.Dynamic);
82         mesh.setIndices(indices);
83         mesh.setVertices(vertices);
84         mesh.sendAttributes();
85         this.setColor(defaultColor);
86 
87         if(camera is null)
88             camera = new HipOrthoCamera();
89         this.camera = camera;
90 
91     }
92 
93     protected pragma(inline) void checkVerticesCount(int howMuch)
94     {
95         if(verticesCount+howMuch >= this.vertices.length/HipGeometryBatchVertex.floatCount)
96             flush();
97     }
98 
99     void setCurrentDepth(float depth){managedDepth = depth;}
100 
101 
102     /**
103     * Adds a vertex to the structure and return its current index.
104     */
105     index_t addVertex(float x, float y, float z)
106     {
107         if(currentColor.a == 0) return verticesCount;
108         vertices[verticesCount] = HipGeometryBatchVertex(
109             Vector3(x,y,z),
110             currentColor
111         );
112         return verticesCount++;
113     }
114 
115     void addIndex(index_t[] newIndices ...)
116     {
117         if(currentColor.a == 0) return;
118         if(currentIndex+newIndices.length >= this.indices.length)
119         {
120             import hip.util.string;
121             String s = String("Too many indices ", currentIndex+1, " for a buffer of size ", this.indices.length);
122             ErrorHandler.assertExit(false, s.toString);
123         }
124         foreach(index; newIndices)
125             indices[currentIndex++] = index;
126     }
127     void setColor(HipColor c)
128     {
129         assert(c != HipColor.no, "Can't use 'no' color on geometry batch");
130         currentColor = c;
131     }
132 
133     protected void triangleVertices(int x1, int y1, int x2, int y2, int x3, int y3)
134     {
135         checkVerticesCount(3);
136         addVertex(x1, y1, managedDepth);
137         addVertex(x2, y2, managedDepth);
138         addVertex(x3, y3, managedDepth);
139         addIndex(
140             cast(index_t)(verticesCount-3),
141             cast(index_t)(verticesCount-2),
142             cast(index_t)(verticesCount-1)
143         );
144     }
145 
146 
147     protected void fillEllipseVertices(int x, int y, int radiusW, int radiusH, int degrees, int startDegrees ,int precision)
148     {
149         // assert(precision >= 3, "Can't have a circle with less than 3 vertices");
150 
151         //Normalize the precision for iterating it on the loop,
152         //Multiply by degrees * DEG_TO_RAD
153         float angle_mult = (1.0/precision) * (degrees) * (PI/180.0);
154 
155         float startAngle = (PI/180.0) * startDegrees;
156 
157         checkVerticesCount(2);
158         index_t centerIndex = addVertex(x, y, managedDepth);
159         //The first vertex
160         index_t lastVert = addVertex(x + radiusW*cos(startAngle), y + radiusH*sin(startAngle), managedDepth);
161         index_t firstVert = lastVert;
162 
163         checkVerticesCount(precision);
164         for(int i = 0; i < precision; i++)
165         {
166             //Divide degrees for the total iterations
167             float nextAngle = (i+1)*angle_mult + startAngle;
168 
169             //Use a temporary variable to hold the new lastVert for more performance
170             //on addIndex calls
171             index_t tempNewLastVert = addVertex(x+radiusW*cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth);
172 
173             addIndex(
174                 centerIndex, //Puts the center first
175                 lastVert, //Appends the vertex from the last iteration
176                 tempNewLastVert//Appends the next vertex
177             );
178             //Updates the last iteration with the next vertex
179             lastVert = tempNewLastVert;
180         }
181 
182         addIndex(
183             centerIndex,
184             lastVert,
185             firstVert
186         );
187     }
188 
189 
190 
191     void drawEllipse(int x, int y, int radiusW, int radiusH, int degrees = 360, HipColor color = HipColor.no, int precision = 24)
192     {
193         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
194         if(HipRenderer.getMode != HipRendererMode.line)
195         {
196             flush();
197             HipRenderer.setRendererMode(HipRendererMode.line);
198         }
199         float angle_mult = (1.0/precision) * degrees * (PI/180.0);
200         checkVerticesCount(1);
201         index_t currVert = addVertex(x+ radiusW*cos(0.0), y + radiusH*sin(0.0), managedDepth);
202         index_t firstVert = currVert;
203 
204         checkVerticesCount(precision);
205         for(int i = 1; i < precision+1; i++)
206         {
207             float nextAngle = angle_mult * i;
208             index_t tempNextVert = addVertex(x + radiusW * cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth);
209 
210             addIndex(currVert, tempNextVert);
211             currVert = tempNextVert;
212         }
213 
214         addIndex(firstVert, currVert);
215         setColor(oldColor);
216     }
217 
218     private HipColor setColorIfChangedAndGetOldColor(in HipColor color)
219     {
220         HipColor oldColor = currentColor;
221         if(color != HipColor.no)
222             setColor(color);
223         return oldColor;
224     }
225 
226     ///With this default precision, the circle should be smooth enough
227     void fillEllipse(int x, int y, int radiusW, int radiusH = -1, int degrees = 360, HipColor color = HipColor.no, int precision = 24)
228     {
229         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
230         if(radiusH == -1)
231             radiusH = radiusW;
232         if(HipRenderer.getMode != HipRendererMode.triangles)
233         {
234             flush();
235             HipRenderer.setRendererMode(HipRendererMode.triangles);
236         }
237         fillEllipseVertices(x, y, radiusW, radiusH, degrees, 0, precision);
238         setColor(oldColor);
239     }
240 
241     void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no)
242     {
243         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
244         if(HipRenderer.getMode != HipRendererMode.triangles)
245         {
246             flush();
247             HipRenderer.setRendererMode(HipRendererMode.triangles);
248         }
249         triangleVertices(x1,y1,x2,y2,x3,y3);
250         setColor(oldColor);
251     }
252     void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no)
253     {
254         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
255         if(HipRenderer.getMode != HipRendererMode.lineStrip)
256         {
257             flush();
258             HipRenderer.setRendererMode(HipRendererMode.lineStrip);
259         }
260         triangleVertices(x1, y1, x2, y2, x3, y3);
261         setColor(oldColor);
262     }
263 
264     void drawLine(int x1, int y1, int x2, int y2, HipColor color = HipColor.no)
265     {
266         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
267         if(HipRenderer.getMode != HipRendererMode.line)
268         {
269             flush();
270             HipRenderer.setRendererMode(HipRendererMode.line);
271         }
272         checkVerticesCount(2);
273         addVertex(x1, y1, managedDepth);
274         addVertex(x2, y2, managedDepth);
275 
276         addIndex(
277             cast(index_t)(verticesCount-2),
278             cast(index_t)(verticesCount-1)
279         );
280         setColor(oldColor);
281     }
282 
283     void drawLine(float x1, float y1, float x2, float y2, HipColor color = HipColor.no)
284     {
285         drawLine(
286             cast(int)x1,
287             cast(int)y1,
288             cast(int)x2,
289             cast(int)y2,
290             color
291         );
292     }
293 
294     void drawQuadraticBezierLine(int x0, int y0, int x1, int y1, int x2, int y2, int precision=24, HipColor color = HipColor.no)
295     {
296         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
297 
298         Vector2 last = Vector2(x0, y0);
299 
300         float precisionMultiplier = 1.0f/precision;
301 
302         for(int i = 0; i <= precision; i++)
303         {
304             float t = cast(float)i*precisionMultiplier;
305             float tNext = t+precisionMultiplier;
306             Vector2 bz = quadraticBezier(x0, y0, x1, y1, x2, y2, tNext);
307             drawLine(last.x, last.y, bz.x, bz.y);
308             last = bz;
309         }
310         drawLine(last.x, last.y, x2, y2);
311         setColor(oldColor);
312     }
313 
314     void drawPixel(int x, int y, HipColor color = HipColor.no)
315     {
316         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
317         if(HipRenderer.getMode != HipRendererMode.point)
318         {
319             flush();
320             HipRenderer.setRendererMode(HipRendererMode.point);
321         }
322         checkVerticesCount(1);
323         addVertex(x, y, managedDepth);
324         addIndex(verticesCount);
325         setColor(oldColor);
326     }
327 
328     /**
329     *   Draws the following rectangle scheme:
330     *  0 _______ 3
331     *   |       |
332     *   |       |
333     *   |_______|
334     *  1        2
335     *   0, 1, 2
336     *   2, 3, 0
337     */
338     pragma(inline, true)
339     protected void rectangleVertices(int x, int y, int w, int h)
340     {
341         checkVerticesCount(4);
342         index_t topLeft = addVertex(x, y, managedDepth);
343         index_t botLeft = addVertex(x, y+h, managedDepth);
344         index_t botRight= addVertex(x+w, y+h, managedDepth);
345         index_t topRight= addVertex(x+w, y, managedDepth);
346 
347         addIndex(
348             topLeft, botLeft, botRight,
349             botRight, topRight, topLeft
350         );
351 
352     }
353 
354     void drawRectangle(int x, int y, int w, int h, HipColor color = HipColor.no)
355     {
356         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
357         if(HipRenderer.getMode != HipRendererMode.lineStrip)
358         {
359             flush();
360             HipRenderer.setRendererMode(HipRendererMode.lineStrip);
361         }
362         rectangleVertices(x,y,w,h);
363         setColor(oldColor);
364     }
365 
366     void fillRoundRect(int x, int y, int w, int h, int radius = 4, HipColor color = HipColor.no, int vertices = 16)
367     {
368         if(radius == 0)
369             return fillRectangle(x,y,w,h,color);
370         if(HipRenderer.getMode != HipRendererMode.triangles)
371         {
372             flush();
373             HipRenderer.setRendererMode(HipRendererMode.triangles);
374         }
375         int vPerEdge = vertices/4;
376         int r2 = radius*2;
377         HipColor old = setColorIfChangedAndGetOldColor(color);
378 
379         ///Draw internal rect.
380         rectangleVertices(x+radius, y+radius, w-r2, h-r2);
381 
382         ///Draw ellipses and also draw border rects
383         //Top Left
384         fillEllipseVertices(x+radius, y+radius, radius, radius, 90, 180, vPerEdge);
385         rectangleVertices(x+radius, y, w - r2, radius);
386         //Top Right
387         fillEllipseVertices(x+w-radius, y+radius, radius, radius, 90, 270, vPerEdge);
388         rectangleVertices(x+w-radius, y+radius, radius, h-r2);
389         // Bottom Right
390         fillEllipseVertices(x+w-radius, y+h-radius, radius, radius, 90, 0, vPerEdge);
391         rectangleVertices(x+radius, y+h-radius, w-r2, radius);
392         //Bottom Left
393         fillEllipseVertices(x+radius, y+h-radius, radius, radius, 90, 90, vPerEdge);
394         rectangleVertices(x, y+radius, radius, h-r2);
395 
396         setColor(old);
397     }
398 
399 
400     void fillRectangle(int x, int y, int w, int h, HipColor color = HipColor.no)
401     {
402         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
403         if(HipRenderer.getMode != HipRendererMode.triangles)
404         {
405             flush();
406             HipRenderer.setRendererMode(HipRendererMode.triangles);
407         }
408         rectangleVertices(x,y,w,h);
409         setColor(oldColor);
410     }
411 
412     void draw()
413     {
414         const uint count = this.currentIndex;
415         import hip.console.log;
416 
417         if(count - lastIndexDrawn != 0)
418         {
419             mesh.bind();
420             mesh.updateVertices(cast(float[])vertices[lastVertexDrawn..verticesCount], lastVertexDrawn);
421             mesh.updateIndices(indices[lastIndexDrawn..currentIndex], lastIndexDrawn);
422 
423             mesh.shader.setFragmentVar("FragVars.uGlobalColor", cast(float[4])[1,1,1,1], true);
424             mesh.shader.setVertexVar("Geom.uMVP",  camera.getMVP, true);
425 
426             mesh.shader.sendVars();
427             //Vertices to render = indices.length
428             this.mesh.draw(count - lastIndexDrawn, HipRenderer.getMode, lastIndexDrawn);
429             mesh.unbind();
430         }
431         lastIndexDrawn = count;
432         lastVertexDrawn = verticesCount;
433     }
434 
435     void flush()
436     {
437         draw();
438         lastVertexDrawn = verticesCount = 0;
439         lastIndexDrawn = currentIndex = 0;
440     }
441 
442 }